home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Format CD 52
/
Amiga Format AFCD52 (Issue 136, May 2000).iso
/
-in_the_mag-
/
multitasking
/
priorities
/
priman
/
source
/
main.c
< prev
next >
Wrap
C/C++ Source or Header
|
2000-03-05
|
19KB
|
587 lines
/*
* Task Priority Manager
* Copyright 1993, 1994 Barry McConnell
* bmccnnll@tcd.ie
*
* The main program loop
* Set tab stops to 4 when editing this file.
*/
#define MAIN /* tells header file to define (as opposed to declare) variables */
#include "PriMan.h"
long __oslibversion = 36; /* tell SAS/C what library versions we need */
static UBYTE version[] = "$VER: PriMan "VERSION" ("DATE")"; /* version string */
/*
* First we have some global variables. These would normally go in the
* header file, but it's easier to define structures with initial data
* here. First is the broker structure for the Commodity interface.
*/
struct NewBroker newBroker =
{
NB_VERSION,
"PriMan",
"PriMan "VERSION" by Barry McConnell",
"Task priority manager",
NBU_UNIQUE | NBU_NOTIFY,
COF_SHOW_HIDE,
0, 0, 0
};
/*
* The starting point for PriMan. First we need to parse the ToolTypes and
* initialiase some variables. Then we set up the Commodity interface if
* necessary. And finally, we handle all the possible messages that can
* arrive at our ports. SAS/C kindly opens all the libraries for us, so we
* don't need to worry about that...
*/
void main(int argc, char *argv[])
{
BOOL more, /* might be more messages waiting for us */
help; /* user has asked for help at startup time */
ULONG signal, /* signal type received */
class, /* message type received */
winSignal, /* window port signal bit */
appSignal, /* AppIcon port signal bit */
cxSignal; /* Commodities port signal bit */
UWORD shift, /* Shift key was pressed */
ctrl; /* Ctrl key was pressed */
WORD code, /* code within message structure */
key, /* VANILLAKEY code, converted to lower-case */
raw; /* RAWKEY code */
struct Message *message; /* message received from port */
struct IntuiMessage *imsg; /* GadTools-parsed message */
struct AmigaGuideMsg *amsg; /* AmigaGuide-parsed message */
struct Window *window; /* window the message refers to */
struct Gadget *selectedGad; /* gadget the message refers to */
/*
* Template for the error messages PriMan can give before exiting.
*/
struct EasyStruct errorMessage =
{
sizeof(struct EasyStruct),
0,
"PriMan Fatal Error",
"%s",
"Okay"
};
/*
* Check what OS version we're running under, so we can take advantage
* of some new 3.x features where possible.
*/
osver = SysBase -> LibNode.lib_Version;
/*
* Create the three message ports that PriMan needs to talk to the
* outside world with, and note their signal bits.
*/
winPort = CreateMsgPort();
appPort = CreateMsgPort();
cxPort = CreateMsgPort();
winSignal = 1L << winPort -> mp_SigBit;
appSignal = 1L << appPort -> mp_SigBit;
cxSignal = 1L << cxPort -> mp_SigBit;
/*
* We need to get the TextAttr structures to point to their font names.
*/
propTA.ta_Name = propName;
monoTA.ta_Name = monoName;
/*
* Here we set up the defaults for all the settings variables, in case
* the user doesn't change them. Note that the font information get set
* to 0 on startup by the compiler.
*/
winLeft = winTop = -1;
winWidth = winHeight = 0;
iconLeft = iconTop = priority = 0;
confirm = commodity = popup = iconify = TRUE;
refresh = SMARTWINDOW;
open = DEFAULTSCREEN;
strcpy(hotkey, "control alt p");
help = FALSE; /* default is no immediate online help */
/*
* When being iconfied, we use another icon file, which gets filled in
* with PriMan's icon imagery below if possible. This call should never
* fail, so I don't do any checking on it.
*/
wbIcon = GetDefDiskObject(WBTOOL);
/*
* Before parsing the ToolTypes, we must open PriMan's .info file. If
* we're running from the Workbench (argc is 0), we take the filename
* from the WBStartup structure. From the Shell, we use argv[0]. We
* then prefix this by PROGDIR: to form a full pathname.
*/
sprintf(myName, "PROGDIR:%s",
argc ? argv[0] : ((struct WBStartup *)argv) -> sm_ArgList -> wa_Name);
if (myIcon = GetDiskObject(myName))
{
if (myIcon -> do_Type != WBTOOL)
{
/*
* Sanity failure! We've found a .info file, but it doesn't
* really belong to PriMan.
*/
FreeDiskObject(myIcon);
myIcon = NULL;
}
else
{
/*
* The correct .info file is open, so we first parse the
* ToolTypes from it, and then copy the imagery across to the
* AppIcon.
*/
HandleToolTypes(myIcon -> do_ToolTypes, &help);
wbIcon -> do_Gadget.Flags = myIcon -> do_Gadget.Flags;
wbIcon -> do_Gadget.Width = myIcon -> do_Gadget.Width;
wbIcon -> do_Gadget.Height = myIcon -> do_Gadget.Height;
wbIcon -> do_CurrentX = iconLeft;
wbIcon -> do_CurrentY = iconTop;
wbIcon -> do_Gadget.GadgetRender = myIcon -> do_Gadget.GadgetRender;
wbIcon -> do_Gadget.SelectRender = myIcon -> do_Gadget.SelectRender;
}
}
/*
* If we're running from the Shell, there may be additional arguments.
*/
if (argc)
{
/*
* It's possible the user is just enquiring as to what PriMan is
* all about (first argument is a question mark), so we print out
* some info and exit.
*/
if (argc > 1 && !strcmp(argv[1], "?"))
{
Print( "PriMan " VERSION " by Barry McConnell - The Task Priority Manager\n" \
"Usage: PriMan [COMMODITY=YES|NO] [CX_POPUP=YES|NO] [CX_POPKEY=hotkey]\n" \
" [CX_PRIORITY=priority] [LEFT=left edge] [TOP=top edge]\n" \
" [WIDTH=width] [HEIGHT=height] [GADFONT=font name]\n" \
" [GADSIZE=font size] [LISTFONT=font name] [LISTSIZE=font size]\n" \
" [REFRESH=SMART|SIMPLE] [SCREEN=DEFAULT|FRONT] [ICONLEFT=left edge]\n"\
" [ICONTOP=top edge] [CONFIRM=YES|NO] [ICONIFY=YES|NO]\n" \
" [TOOLPRI=task priority]\n" \
"Type \"PriMan HELP\" to get AmigaGuide help.\n");
error = ALL_OKAY;
}
else
HandleToolTypes(argv, &help);
}
/*
* Now we install ourselves as a Commodity, assuming the error variable
* wasn't set above. If there is an existing PriMan running as a
* Commodity, error will be set here, and the main loop below will fall
* out immediately.
*/
if (!error)
{
newBroker.nb_Port = cxPort;
newBroker.nb_Pri = priority;
if (commodity)
SetupCommodity();
if (!error)
{
/*
* Since pos gets initialised to 0 by the compiler, and there
* won't actually be a task selected to start with, we change
* it to -1 here. This isn't strictly necessary (since some
* code later on does this for us), but it tells the function
* which creates the main window's gadgetry to disable some
* buttons when they are being created, so we avoid an initial
* "flashing" the first time the window is opened.
*/
pos = -1;
/*
* Here we define what happens on startup. If the user does not
* want us to "popup when launched", and we are running as a
* Commodity or can be iconified, then we don't open a window,
* and instead create an AppIcon if necessary. Otherwise, we
* must open the main window.
*/
if (!popup && (iconify || commodity))
Iconify();
else
Show();
if (!error)
/*
* If the user wants online help, we wait until here to
* fire up AmigaGuide (in case something went wrong setting
* stuff up above).
*/
if (help)
Help();
}
}
/*
* This is the heart of PriMan. As long as nothing has gone wrong, we
* go round in a loop waiting for a message from any of our three
* ports, the AmigaGuide port (if applicable), or a Ctrl-C/F signal,
* handle that message, then loop back.
*/
while (!error)
{
signal = Wait(winSignal | appSignal | cxSignal | guideSignal |
SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_F);
do
{
/*
* We use the "more" variable to let us know when to stop
* checking each port for more messages. If it's still set to
* FALSE at the end of the loop, it means all ports are now
* empty, and we can go back to sleep.
*/
more = FALSE;
/*
* If we get a Ctrl-C signal, we immediately abort the
* while loop, without checking the other ports.
*/
if (signal & SIGBREAKF_CTRL_C)
{
error = ALL_OKAY;
break;
}
/*
* A Ctrl-F signal means we should make ourselves visible.
*/
if (signal & SIGBREAKF_CTRL_F)
Show();
/*
* The only possible message from the AppIcon's port will
* tell us to wake up, so if we receive it, we open the
* main window, and reply to the message.
*/
if (signal & appSignal && (message = GetMsg(appPort)))
{
ReplyMsg(message);
Show();
more = TRUE;
}
/*
* We need to handle lots of difference cases if we get a
* message from the Commodities port. We might need to
* open (or bring to the front) the main window if the
* Show button was pressed in Exchange, or the hotkey was
* pressed, or another PriMan tried installing itself as a
* Commodity. We might need to hide our windows if the Hide
* button was pressed in Exchange. And finally, if Exchange
* sends us a Kill message, we must close down. Any other
* message types are ignored.
*/
if (signal & cxSignal && (message = GetMsg(cxPort)))
{
switch (class = CxMsgType((CxMsg *)message))
{
case CXM_COMMAND:
switch (CxMsgID((CxMsg *)message))
{
case CXCMD_APPEAR:
case CXCMD_UNIQUE:
Show();
break;
case CXCMD_DISAPPEAR:
Hide();
break;
case CXCMD_KILL:
error = ALL_OKAY;
break;
}
break;
case CXM_IEVENT: /* user pressed hotkey */
Show();
break;
}
ReplyMsg(message);
more = TRUE;
}
/*
* Here is a very basic AmigaGuide handler. We only check for
* one type of message and ignore all the rest - no error
* handling is performed. The message we need to check for is
* ActiveToolID: this means the process spawned in the Help()
* function has successfully opened the PriMan.guide file, and
* is waiting for further instructions. We actually call Help()
* again in this case, as now that we have a valid handle on
* the guide file, that function will jump to the first node
* in it for us.
*/
if (signal & guideSignal && (amsg = GetAmigaGuideMsg(guideHandle)))
{
if (amsg -> agm_Type == ActiveToolID)
Help();
ReplyAmigaGuideMsg(amsg);
more = TRUE;
}
/* This is the fun bit. A message to our window port can
* come from either window. The easiest thing to do is to
* simply pass it onto a separate procedure. But first,
* there is a little bit of complicated stuff involving
* window resizing...
*/
if (signal & winSignal && (imsg = GT_GetIMsg(winPort)))
{
/*
* Make a note of the important information in the
* message.
*/
key = raw = 0;
class = imsg -> Class;
selectedGad = imsg -> IAddress;
code = imsg -> Code;
shift = imsg -> Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT);
ctrl = imsg -> Qualifier & IEQUALIFIER_CONTROL;
window = imsg -> IDCMPWindow;
/*
* There are several events which must be handled before
* replying to the message.
*/
switch (class)
{
/*
* If we get a SIZEVERIFY message, we need to remove
* all the GadTools gadgets before we allow the user
* to move the size gadget. This will prevent Intuition
* doing any unnecessary refreshing, and will also
* ensure the window's borders don't get clobbered.
*/
case IDCMP_SIZEVERIFY:
RemoveGList(mainWindow, mainGads, -1);
window = NULL;
break;
/*
* A NEWSIZE message generally means all the gadgets
* must be deallocated, recreated, and added back into
* the window. However, if the window size did not
* actually change, there is no need to do this, and
* instead we just quietly add back in the existing
* gadget list (which didn't actually get deallocated
* on SIZEVERIFY).
*/
case IDCMP_NEWSIZE:
if (winWidth == mainWindow -> Width && winHeight == mainWindow -> Height)
AddGList(mainWindow, mainGads, ~0, -1, NULL);
else
{
GetListTop(); /* so we can restore it later */
FreeGadgets(mainGads);
mainGads = NULL;
OpenMainWindow();
}
window = NULL;
break;
/*
* In addition, while we could pass on the INTUITICKS
* messages to the appropriate procedures, these
* actually do nothing with this event, so in order
* not to waste CPU time, we ignore this event by
* setting the window variable to NULL (this is also
* done for the two events above, as they have been
* appropriately handled right here).
*/
case IDCMP_INTUITICKS:
window = NULL;
break;
/*
* We want to do a little mapping of VANILLAKEY codes.
* If Shift is being pressed, we must set bit 5 of the
* keycode to convert it to lower-case. For Ctrl, we
* ensure both bits 5 and 6 are set. The end result is
* a lower-case version of any keypress handled by this
* Intuition event. (Check an ASCII table to see how
* keypresses come out with these modifiers!)
*/
case IDCMP_VANILLAKEY:
key = code | (shift ? 32 : 0) | (ctrl ? 96 : 0);
break;
/*
* RAWKEY codes don't need mapping, and in fact we only
* use these for the Help key!
*/
case IDCMP_RAWKEY:
raw = code;
break;
}
GT_ReplyIMsg(imsg);
more = TRUE;
/*
* Now we despatch the message to another function if
* it is still necessary (i.e. the window variable
* wasn't cleared above).
*/
if (window == mainWindow)
HandleMainWindow(class, code, key, raw, shift, ctrl, selectedGad);
else if (window == setWindow)
HandleSettingsWindow(class, code, key, raw, shift, selectedGad);
}
}
while (more); /* could be more messages waiting for us */
}
/*
* If we've got this far, either something bad happened, or the "all
* okay" error condition was raised (meaning the user wanted us to
* quit). We need to shut down windows, free up gadgets, deallocate
* memory, and free up other system stuff. Then - if necessary - an
* error requester is displayed.
*/
CloseSettingsWindow();
CloseMainWindow();
FreeRemember(&memoryKey, TRUE);
DeleteCxObjAll(broker);
FreeAslRequest(propFontReq);
FreeAslRequest(monoFontReq);
if (guideHandle)
CloseAmigaGuide(guideHandle);
CloseLibrary(AmigaGuideBase);
if (appIcon)
RemoveAppIcon(appIcon);
if (wbIcon)
FreeDiskObject(wbIcon);
if (myIcon)
FreeDiskObject(myIcon);
WipePort(winPort);
WipePort(appPort);
WipePort(cxPort);
switch (error) /* there does not necessarily have to be one */
{
case LOCK_ERROR:
SimpleRequest(&errorMessage, "Can't lock default public screen.");
break;
case VISINFO_ERROR:
SimpleRequest(&errorMessage, "Can't get VisualInfo for this screen.");
break;
case FONT_ERROR:
SimpleRequest(&errorMessage, "Can't open your selected fonts.");
break;
case GADGET_ERROR:
SimpleRequest(&errorMessage, "Can't create list of gadgets.\n(Out of memory?)");
break;
case MAIN_ERROR:
SimpleRequest(&errorMessage, "Can't open main window.\n(Out of memory?)");
break;
case TASK_ERROR:
SimpleRequest(&errorMessage, "Can't create list of tasks.\n(Out of memory?)");
break;
case SETTINGS_ERROR:
SimpleRequest(&errorMessage, "Can't open settings window.\n(Out of memory?)");
break;
case CX_ERROR:
SimpleRequest(&errorMessage, "Can't set up Commodity interface.");
break;
case MENU_ERROR:
SimpleRequest(&errorMessage, "Can't create menus.\n(Out of memory?)");
break;
}
}
/*
* Since the user can pass settings information in two ways (ToolTypes and
* Shell arguments), we put the code to parse them in a separate function.
* It takes in an array of ToolTypes (from the .info file) or Shell
* arguments (from argv[]) and examines their contents using ArgInt() or
* ArgString(). If HELP is one of those keywords, it sets the help flag
* passed in.
*/
void HandleToolTypes(char **tools, BOOL *help)
{
/*
* For the numeric settings, if a ToolType can't be found, we use the
* existing value of the settings variable. (Which will either be the
* internal default, or - if we're examining Shell arguments - the
* ToolType value from the last time this function was called.)
*/
winLeft = (WORD)ArgInt(tools, "LEFT", winLeft);
winTop = (WORD)ArgInt(tools, "TOP", winTop);
winWidth = (WORD)ArgInt(tools, "WIDTH", winWidth);
winHeight = (WORD)ArgInt(tools, "HEIGHT", winHeight);
propTA.ta_YSize = (UWORD)ArgInt(tools, "GADSIZE", propTA.ta_YSize);
monoTA.ta_YSize = (UWORD)ArgInt(tools, "LISTSIZE", monoTA.ta_YSize);
iconLeft = (WORD)ArgInt(tools, "ICONLEFT", iconLeft);
iconTop = (WORD)ArgInt(tools, "ICONTOP", iconTop);
priority = (BYTE)ArgInt(tools, "CX_PRIORITY", priority);
/*
* The string settings are handled similarly.
*/
strcpy(propName, ArgString(tools, "GADFONT", propName));
strcpy(monoName, ArgString(tools, "LISTFONT", monoName));
strncpy(hotkey, ArgString(tools, "CX_POPKEY", hotkey), MaxHotkey);
/*
* The YES/NO settings follow. Since the defaults are all YES, we bias
* towards this, and only allow a NO if the first letter is 'n'. (So
* "xyz" would be YES.)
*/
confirm = (ArgString(tools, "CONFIRM", confirm ? "Y" : "N")[0] & ~32) != 'N';
commodity = (ArgString(tools, "COMMODITY", commodity ? "Y" : "N")[0] & ~32) != 'N';
popup = (ArgString(tools, "CX_POPUP", popup ? "Y" : "N")[0] & ~32) != 'N';
iconify = (ArgString(tools, "ICONIFY", iconify ? "Y" : "N")[0] & ~32) != 'N';
/*
* Our defaults for Window Type and Open On are SMART and DEFAULT,
* respectively. As above, we are biased, and only change these if the
* user enters exactly the text SIMPLE or FRONT.
*/
refresh = !Compare(ArgString(tools, "REFRESH", refresh == SMARTWINDOW ? "SMART" : "SIMPLE"), "SIMPLE");
open = !Compare(ArgString(tools, "SCREEN", open == DEFAULTSCREEN ? "DEFAULT" : "FRONT"), "FRONT");
/*
* Here we handle the TOOLPRI setting. (This may already have been
* done if we were launched from Workbench.) We examine our own task
* structure to see what priority we are at currently, and "change" to
* that if no ToolType was given.
*/
SetTaskPri(FindTask(NULL), (BYTE)ArgInt(tools, "TOOLPRI", FindTask(NULL) -> tc_Node.ln_Pri));
/*
* Finally, if the user wants help, we set a flag to ensure he gets it
* later. We don't call Help() ourselves now since if there was an
* error during startup, we'd run into real trouble trying to kill the
* AmigaGuide process while it was doing its thing!
*
* If the help flag was already set, obviously we don't need to do the
* test for the HELP keyword again.
*/
*help = *help || FindToolType(tools, "HELP");
}